package service

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"sale_system/inventory_srv/global"
	"sale_system/inventory_srv/model"
)

const (
	// Redis键前缀
	InventoryKeyPrefix = "inventory:"
	InventoryLockPrefix = "inventory_lock:"
	SyncTaskKey = "inventory_sync_tasks"
	
	// 库存操作类型
	ActionUpdate = "update"
	ActionFreeze = "freeze"
	ActionUnfreeze = "unfreeze"
	ActionDeduct = "deduct"
)

type RedisInventoryService struct{}

// NewRedisInventoryService 创建Redis库存服务实例
func NewRedisInventoryService() *RedisInventoryService {
	return &RedisInventoryService{}
}

// GetInventoryKey 获取库存Redis键
func (s *RedisInventoryService) GetInventoryKey(goodsId int32) string {
	return fmt.Sprintf("%s%d", InventoryKeyPrefix, goodsId)
}

// GetInventoryLockKey 获取库存锁Redis键
func (s *RedisInventoryService) GetInventoryLockKey(goodsId int32) string {
	return fmt.Sprintf("%s%d", InventoryLockPrefix, goodsId)
}

// LoadInventoryFromDB 从数据库加载库存到Redis
func (s *RedisInventoryService) LoadInventoryFromDB(goodsId int32) error {
	var inv model.Inventory
	if result := global.DB.Where("goods = ?", goodsId).First(&inv); result.RowsAffected == 0 {
		return status.Error(codes.NotFound, "商品库存不存在")
	}

	redisInv := model.RedisInventory{
		GoodsId:    inv.Goods,
		Stocks:     inv.Stocks,
		Freeze:     inv.Freeze,
		Version:    inv.Version,
		UpdateTime: time.Now().Unix(),
	}

	return s.SetInventoryToRedis(goodsId, &redisInv)
}

// GetInventoryFromRedis 从Redis获取库存信息
func (s *RedisInventoryService) GetInventoryFromRedis(goodsId int32) (*model.RedisInventory, error) {
	key := s.GetInventoryKey(goodsId)
	data, err := global.RedisClient.Get(context.Background(), key).Result()
	if err != nil {
		// Redis中没有，尝试从数据库加载
		if err := s.LoadInventoryFromDB(goodsId); err != nil {
			return nil, err
		}
		// 重新获取
		data, err = global.RedisClient.Get(context.Background(), key).Result()
		if err != nil {
			return nil, err
		}
	}

	var redisInv model.RedisInventory
	if err := json.Unmarshal([]byte(data), &redisInv); err != nil {
		return nil, err
	}

	return &redisInv, nil
}

// SetInventoryToRedis 设置库存到Redis
func (s *RedisInventoryService) SetInventoryToRedis(goodsId int32, inv *model.RedisInventory) error {
	key := s.GetInventoryKey(goodsId)
	data, err := json.Marshal(inv)
	if err != nil {
		return err
	}

	// 设置库存，过期时间24小时
	return global.RedisClient.Set(context.Background(), key, data, 24*time.Hour).Err()
}

// UpdateInventory 更新库存（原子操作）
func (s *RedisInventoryService) UpdateInventory(goodsId int32, stocks, freeze int32) error {
	key := s.GetInventoryKey(goodsId)
	
	// 使用Lua脚本保证原子性
	luaScript := `
		local key = KEYS[1]
		local stocks = tonumber(ARGV[1])
		local freeze = tonumber(ARGV[2])
		local updateTime = tonumber(ARGV[3])
		
		local data = redis.call('GET', key)
		if not data then
			return {err = "inventory not found"}
		end
		
		local inv = cjson.decode(data)
		inv.stocks = stocks
		inv.freeze = freeze
		inv.update_time = updateTime
		inv.version = inv.version + 1
		
		redis.call('SET', key, cjson.encode(inv), 'EX', 86400)
		return {ok = "success"}
	`
	
	result, err := global.RedisClient.Eval(context.Background(), luaScript, []string{key}, stocks, freeze, time.Now().Unix()).Result()
	if err != nil {
		return err
	}
	
	if resultMap, ok := result.([]interface{}); ok && len(resultMap) > 0 {
		if resultMap[0] == "err" {
			return fmt.Errorf("update inventory failed: %v", resultMap[1])
		}
	}
	
	return nil
}

// DeductInventory 扣减库存（秒杀场景）
func (s *RedisInventoryService) DeductInventory(goodsId int32, num int32, orderSn string) error {
	key := s.GetInventoryKey(goodsId)
	
	// 使用Lua脚本保证原子性
	luaScript := `
		local key = KEYS[1]
		local deductNum = tonumber(ARGV[1])
		local orderSn = ARGV[2]
		local updateTime = tonumber(ARGV[3])
		
		local data = redis.call('GET', key)
		if not data then
			return {err = "inventory not found"}
		end
		
		local inv = cjson.decode(data)
		local availableStock = inv.stocks - inv.freeze
		
		if availableStock < deductNum then
			return {err = "insufficient stock"}
		end
		
		-- 扣减实际库存
		inv.stocks = inv.stocks - deductNum
		inv.update_time = updateTime
		inv.version = inv.version + 1
		
		redis.call('SET', key, cjson.encode(inv), 'EX', 86400)
		
		-- 记录操作日志
		local logKey = "inventory_log:" .. orderSn
		local logData = {
			goods_id = inv.goods_id,
			action = "deduct",
			amount = deductNum,
			order_sn = orderSn,
			before_stocks = inv.stocks + deductNum,
			after_stocks = inv.stocks,
			before_freeze = inv.freeze,
			after_freeze = inv.freeze,
			source = "redis",
			timestamp = updateTime
		}
		redis.call('LPUSH', logKey, cjson.encode(logData))
		redis.call('EXPIRE', logKey, 3600) -- 1小时过期
		
		return {ok = "success", available_stock = availableStock - deductNum}
	`
	
	result, err := global.RedisClient.Eval(context.Background(), luaScript, []string{key}, num, orderSn, time.Now().Unix()).Result()
	if err != nil {
		return err
	}
	
	if resultMap, ok := result.([]interface{}); ok && len(resultMap) > 0 {
		if resultMap[0] == "err" {
			return fmt.Errorf("deduct inventory failed: %v", resultMap[1])
		}
	}
	
	// 添加同步任务
	s.AddSyncTask(goodsId, ActionDeduct, num, orderSn)
	
	return nil
}

// FreezeInventory 冻结库存（预扣减）
func (s *RedisInventoryService) FreezeInventory(goodsId int32, num int32, orderSn string) error {
	key := s.GetInventoryKey(goodsId)
	
	luaScript := `
		local key = KEYS[1]
		local freezeNum = tonumber(ARGV[1])
		local orderSn = ARGV[2]
		local updateTime = tonumber(ARGV[3])
		
		local data = redis.call('GET', key)
		if not data then
			return {err = "inventory not found"}
		end
		
		local inv = cjson.decode(data)
		local availableStock = inv.stocks - inv.freeze
		
		if availableStock < freezeNum then
			return {err = "insufficient stock"}
		end
		
		-- 增加冻结库存
		inv.freeze = inv.freeze + freezeNum
		inv.update_time = updateTime
		inv.version = inv.version + 1
		
		redis.call('SET', key, cjson.encode(inv), 'EX', 86400)
		
		-- 记录操作日志
		local logKey = "inventory_log:" .. orderSn
		local logData = {
			goods_id = inv.goods_id,
			action = "freeze",
			amount = freezeNum,
			order_sn = orderSn,
			before_stocks = inv.stocks,
			after_stocks = inv.stocks,
			before_freeze = inv.freeze - freezeNum,
			after_freeze = inv.freeze,
			source = "redis",
			timestamp = updateTime
		}
		redis.call('LPUSH', logKey, cjson.encode(logData))
		redis.call('EXPIRE', logKey, 3600)
		
		return {ok = "success", available_stock = availableStock - freezeNum}
	`
	
	result, err := global.RedisClient.Eval(context.Background(), luaScript, []string{key}, num, orderSn, time.Now().Unix()).Result()
	if err != nil {
		return err
	}
	
	if resultMap, ok := result.([]interface{}); ok && len(resultMap) > 0 {
		if resultMap[0] == "err" {
			return fmt.Errorf("freeze inventory failed: %v", resultMap[1])
		}
	}
	
	// 添加同步任务
	s.AddSyncTask(goodsId, ActionFreeze, num, orderSn)
	
	return nil
}

// UnfreezeInventory 解冻库存
func (s *RedisInventoryService) UnfreezeInventory(goodsId int32, num int32, orderSn string) error {
	key := s.GetInventoryKey(goodsId)
	
	luaScript := `
		local key = KEYS[1]
		local unfreezeNum = tonumber(ARGV[1])
		local orderSn = ARGV[2]
		local updateTime = tonumber(ARGV[3])
		
		local data = redis.call('GET', key)
		if not data then
			return {err = "inventory not found"}
		end
		
		local inv = cjson.decode(data)
		
		if inv.freeze < unfreezeNum then
			return {err = "insufficient frozen stock"}
		end
		
		-- 减少冻结库存
		inv.freeze = inv.freeze - unfreezeNum
		inv.update_time = updateTime
		inv.version = inv.version + 1
		
		redis.call('SET', key, cjson.encode(inv), 'EX', 86400)
		
		-- 记录操作日志
		local logKey = "inventory_log:" .. orderSn
		local logData = {
			goods_id = inv.goods_id,
			action = "unfreeze",
			amount = unfreezeNum,
			order_sn = orderSn,
			before_stocks = inv.stocks,
			after_stocks = inv.stocks,
			before_freeze = inv.freeze + unfreezeNum,
			after_freeze = inv.freeze,
			source = "redis",
			timestamp = updateTime
		}
		redis.call('LPUSH', logKey, cjson.encode(logData))
		redis.call('EXPIRE', logKey, 3600)
		
		return {ok = "success"}
	`
	
	result, err := global.RedisClient.Eval(context.Background(), luaScript, []string{key}, num, orderSn, time.Now().Unix()).Result()
	if err != nil {
		return err
	}
	
	if resultMap, ok := result.([]interface{}); ok && len(resultMap) > 0 {
		if resultMap[0] == "err" {
			return fmt.Errorf("unfreeze inventory failed: %v", resultMap[1])
		}
	}
	
	// 添加同步任务
	s.AddSyncTask(goodsId, ActionUnfreeze, num, orderSn)
	
	return nil
}

// AddSyncTask 添加同步任务到队列
func (s *RedisInventoryService) AddSyncTask(goodsId int32, action string, amount int32, orderSn string) {
	task := model.InventorySyncTask{
		GoodsId:   goodsId,
		Action:    action,
		Amount:    amount,
		OrderSn:   orderSn,
		Timestamp: time.Now().Unix(),
	}
	
	taskData, err := json.Marshal(task)
	if err != nil {
		zap.S().Errorf("序列化同步任务失败: %v", err)
		return
	}
	
	// 添加到同步队列
	err = global.RedisClient.LPush(context.Background(), SyncTaskKey, taskData).Err()
	if err != nil {
		zap.S().Errorf("添加同步任务失败: %v", err)
	}
}

// GetAvailableStock 获取可用库存
func (s *RedisInventoryService) GetAvailableStock(goodsId int32) (int32, error) {
	inv, err := s.GetInventoryFromRedis(goodsId)
	if err != nil {
		return 0, err
	}
	
	return inv.Stocks - inv.Freeze, nil
}

// SyncToMySQL 同步Redis库存到MySQL
func (s *RedisInventoryService) SyncToMySQL() error {
	// 从队列中获取同步任务
	for {
		result, err := global.RedisClient.BRPop(context.Background(), 1*time.Second, SyncTaskKey).Result()
		if err != nil {
			// 队列为空或超时
			break
		}
		
		if len(result) < 2 {
			continue
		}
		
		var task model.InventorySyncTask
		if err := json.Unmarshal([]byte(result[1]), &task); err != nil {
			zap.S().Errorf("解析同步任务失败: %v", err)
			continue
		}
		
		// 执行同步
		if err := s.executeSyncTask(&task); err != nil {
			zap.S().Errorf("执行同步任务失败: %v", err)
			// 可以选择重试或记录失败日志
		}
	}
	
	return nil
}

// executeSyncTask 执行单个同步任务
func (s *RedisInventoryService) executeSyncTask(task *model.InventorySyncTask) error {
	tx := global.DB.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()
	
	var inv model.Inventory
	if result := tx.Where("goods = ?", task.GoodsId).First(&inv); result.RowsAffected == 0 {
		tx.Rollback()
		return fmt.Errorf("商品库存不存在: %d", task.GoodsId)
	}
	
	beforeStocks := inv.Stocks
	beforeFreeze := inv.Freeze
	
	switch task.Action {
	case ActionDeduct:
		inv.Stocks = inv.Stocks - task.Amount
	case ActionFreeze:
		inv.Freeze = inv.Freeze + task.Amount
	case ActionUnfreeze:
		inv.Freeze = inv.Freeze - task.Amount
	case ActionUpdate:
		// 从Redis获取最新数据
		redisInv, err := s.GetInventoryFromRedis(task.GoodsId)
		if err != nil {
			tx.Rollback()
			return err
		}
		inv.Stocks = redisInv.Stocks
		inv.Freeze = redisInv.Freeze
		inv.Version = redisInv.Version
	}
	
	if err := tx.Save(&inv).Error; err != nil {
		tx.Rollback()
		return err
	}
	
	// 记录操作日志
	log := model.InventoryLog{
		GoodsId:      task.GoodsId,
		Action:       task.Action,
		Amount:       task.Amount,
		OrderSn:      task.OrderSn,
		BeforeStocks: beforeStocks,
		AfterStocks:  inv.Stocks,
		BeforeFreeze: beforeFreeze,
		AfterFreeze:  inv.Freeze,
		Source:       "redis",
		Status:       1,
	}
	
	if err := tx.Create(&log).Error; err != nil {
		tx.Rollback()
		return err
	}
	
	return tx.Commit().Error
}

// StartSyncWorker 启动同步工作协程
func (s *RedisInventoryService) StartSyncWorker(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Second) // 每5秒同步一次
	defer ticker.Stop()
	
	for {
		select {
		case <-ctx.Done():
			zap.S().Info("库存同步工作协程退出")
			return
		case <-ticker.C:
			if err := s.SyncToMySQL(); err != nil {
				zap.S().Errorf("库存同步失败: %v", err)
			}
		}
	}
} 